import os
import random
import re
import threading
import time
import zipfile
from datetime import datetime, timedelta
from pathlib import Path

from PIL import Image
from PyQt5.QtCore import QThread, pyqtSignal

from DBManager import DBManager
from app_config import AppConfig
from pandas import read_csv, read_excel, DataFrame, to_datetime, to_numeric, concat, Series
import xlrd
import openpyxl
import chardet
import pytesseract
from playwright.sync_api import sync_playwright

from utils import read_csv_file, read_excel_file, read_zip_file


class WorkerThread(QThread):
    '''
    获取数据类
    '''
    # 线程信号
    finished = pyqtSignal()  # 结束信号
    status_signal = pyqtSignal(str)  # 状态栏信息

    def __init__(self, logger, min_wait, max_wait, data_type,stroe_box:bool,product_box:bool):
        super().__init__()
        self.logger = logger
        self.min_wait = min_wait
        self.max_wait = max_wait
        self.dataType = data_type
        self.stroeBox = stroe_box
        self.productBox = product_box

    def get_file_to_pd(self, suggested_filename):
        file_path = Path(AppConfig.Path.DOWNLOAD_DIR) / suggested_filename
        if not file_path.exists():
            raise FileNotFoundError(f"该文件不存在: {file_path}")

        df = None
        suffix = file_path.suffix

        if suffix == '.csv':
            df = read_csv_file(file_path, self.detect_encoding)
        elif suffix == '.xlsx':
            df = read_excel_file(file_path)
        elif suffix == '.xls':
            df = read_excel(file_path, engine='xlrd')
        elif suffix == '.zip':
            df = read_zip_file(file_path, self.detect_encoding)

        if df is not None and not df.empty:
            df.fillna('', inplace=True)

        try:
            file_path.unlink(missing_ok=True)
        except Exception:
            pass

        return df

    def parse_value(self, value):
        value = value.strip()
        if not value:  # 处理空值情况
            return 0
        if value == '-':  # 显式处理 '-' 的情况
            return 0
        if '%' in value:  # 百分比直接返回字符串
            return value
        value = value.replace(',', '')  # 去除逗号
        return float(value) if '.' in value else int(value)

    def get_ocr_path(self):
        common_paths = [
            r"C:\Program Files\Tesseract-OCR\tesseract.exe",
            r"C:\Program Files (x86)\Tesseract-OCR\tesseract.exe"
        ]
        for path in common_paths:
            if os.path.exists(path):
                return path
        return None

    def ocr_img_num(self, image_path, exe_path):
        pytesseract.pytesseract.tesseract_cmd = exe_path
        image = Image.open(image_path)
        # OCR 识别（仅允许数字和 .，并去除换行符）
        config = '--psm 6 -c tessedit_char_whitelist=0123456789.,%'
        text = pytesseract.image_to_string(image, config=config).replace("\n", "").strip()
        number = text.replace(",", "").strip()
        return number

    def get_filename(self, filename):
        """清除文件名中的非法字符"""
        return re.sub(r'[\\/*?:"<>|]', "", filename).strip()

    def remove_element(self, page, selector_type: str, name: str):
        """
        移除指定 ID 或类名的页面元素，失败直接跳过。
        :param page: Playwright page 对象
        :param selector_type: 'id' 或 'class'
        :param name: 元素的 id 或 class 名称
        """
        try:
            if selector_type == 'id':
                page.evaluate(f"""
                    () => {{
                        const el = document.getElementById('{name}');
                        if (el) el.remove();
                    }}
                """)
            elif selector_type == 'class':
                page.evaluate(f"""
                    () => {{
                        document.querySelectorAll('.{name}').forEach(el => el.remove());
                    }}
                """)
        except:
            pass  # 出错时直接跳过

    def fill_value(self, page, selector: str, text: str, min_delay=0.1, max_delay=0.9, clear_first=True):
        """
        模拟人类输入
        参数:
            page: Playwright 的 page 实例
            selector: CSS 选择器（如 '#usernameId'）
            text: 要输入的字符串
            min_delay: 单字符输入最小间隔（秒）
            max_delay: 单字符输入最大间隔（秒）
            clear_first: 是否先清空原始内容
        """
        locator = page.locator(selector)
        locator.click()
        if clear_first:
            locator.fill('')
        for char in text:
            locator.type(char, delay=0)  # delay 为 0，手动 sleep 更精细
            time.sleep(random.uniform(min_delay, max_delay))

    def wait_user_input_stable(self,page, username_selector: str, password_selector: str, timeout: int = 300,
                               stable_secs: float = 2.0):
        import time
        start_time = time.time()
        last_input = ("", "")
        last_change_time = time.time()

        while time.time() - start_time < timeout:
            username = page.locator(username_selector).input_value().strip()
            password = page.locator(password_selector).input_value().strip()

            if username and password:
                if (username, password) != last_input:
                    last_input = (username, password)
                    last_change_time = time.time()
                elif time.time() - last_change_time >= stable_secs:
                    return True  # 输入稳定
            time.sleep(0.3)
        return False

    def run(self):
        global context, browser, playwright
        #任务计数:店铺成功数,失败数,商品成功数,失败数
        task_status = {'store_s':0,'store_e':0,'product_s':0,'product_e':0}
        base_dir = os.getcwd()
        download_folder = os.path.join(base_dir, AppConfig.Path.DOWNLOAD_DIR)
        os.makedirs(download_folder, exist_ok=True)


        try:
            exe_path = self.get_ocr_path()
            if exe_path is None:
                raise ValueError('未找到视觉识别依赖库安装路径,请点击[下载依赖库]按钮进行下载安装!')
            db = DBManager()  # 实例化
            stores,products = [],[]
            if self.stroeBox:
                stores = db.get_all_stores_dict(flag=True)
            if self.productBox:
                products = db.get_all_products_dict(flag=True)

            now = datetime.now()
            yesterdayTime = now - timedelta(days=1)
            yesterdayTime7 = now - timedelta(days=7)
            yesterdayTime30 = now - timedelta(days=30)
            today = now.strftime('%Y-%m-%d')  # 今天的日期
            yesterday = yesterdayTime.strftime('%Y-%m-%d')  # 昨天的日期
            yesterday7 = yesterdayTime7.strftime('%Y-%m-%d')  # 7天的日期
            yesterday30 = yesterdayTime30.strftime('%Y-%m-%d')  # 30天的日期
        except Exception as e:
            raise ValueError('任务主数据获取失败')

        if self.dataType == '7日':
            rangeType = "近" + self.dataType
            date_range_text = yesterday7 + '至' + yesterday
        elif self.dataType == '30日':
            rangeType = "近" + self.dataType
            date_range_text = yesterday30 + '至' + yesterday
        else:
            rangeType = self.dataType
            date_range_text = yesterday + '至' + yesterday

        img_path = AppConfig.Path.OCR_DIR
        os.makedirs(img_path, exist_ok=True)

        try:
            playwright = sync_playwright().start()
            browser = playwright.chromium.connect_over_cdp(AppConfig.Chrome.CDP_URL)
            if self.stroeBox:
                for store in stores:
                    self.status_signal.emit(f"开始获取{store['store_name']}-店铺数据")
                    try:
                        context = browser.contexts[0]
                        page = context.pages[0]
                        page.goto(AppConfig.Chrome.PDD_LOGIN_URL)
                        page.wait_for_load_state()
                        time.sleep(random.randint(self.min_wait, self.max_wait))
                        login_title = page.locator("div.tab-item:has-text('账号登录')")
                        if login_title.count() > 0:
                            login_title.click(timeout=3000)
                            page.wait_for_load_state()
                            if store['username'] and store['password']:
                                self.fill_value(page, '#usernameId', store['username'])
                                self.fill_value(page, '#passwordId', store['password'])
                                loginBtn = page.locator('button:has-text("登录")')
                                loginBtn.wait_for(state="visible", timeout=5000)
                                loginBtn.click(timeout=5000)
                                page.wait_for_load_state()
                                time.sleep(random.randint(self.min_wait, self.max_wait))
                            else:
                                self.logger.info("等待用户手动登录店铺(最长等待时间:5分钟)")
                                # 持续等待页面标题为“首页”，最长等待 timeout 秒
                                max_wait = 30  # 最长等待秒数
                                interval = 3  # 每次检查间隔秒数
                                waited = 0

                                while waited < max_wait:
                                    try:
                                        title = page.title()
                                        if "首页" in title:
                                            self.logger.info("检测已登录到店铺，继续执行")
                                            break
                                    except Exception as e:
                                        pass
                                    time.sleep(interval)
                                    waited += interval
                                else:
                                    raise ValueError("等待超时，用户未登录到店铺")
                        page.goto(AppConfig.Chrome.PDD_LLSJ_URL)
                        page.wait_for_load_state()
                        time.sleep(random.randint(self.min_wait, self.max_wait))
                        # 侦测弹框并取消
                        page.on("dialog", lambda dialog: dialog.dismiss())
                        # 检测弹窗是否存在
                        try:
                            # 定位弹窗的关闭按钮
                            close_button = page.query_selector("svg[data-testid='beast-core-icon-close']")
                            if close_button:
                                close_button.click()  # 点击关闭
                            # 移除浏览器检测弹窗
                            self.remove_element(page, 'id', "global_broswer_nav")
                            self.remove_element(page, 'id', "umd_kits_home_entry_wrapper")
                        except Exception as e:
                            pass
                        page.keyboard.press('Escape')

                        date_range = page.locator(f"div.date-picker-item-inner:has-text('{rangeType}')")
                        date_range.click(timeout=3000)
                        page.wait_for_load_state()

                        page.wait_for_selector(".mms-symc-card-item-new__wrapper")
                        time.sleep(random.randint(self.min_wait, self.max_wait))
                        # 获取所有卡片
                        cards = page.locator(".mms-symc-card-item-new__wrapper").all()

                        llsj_dict = {'获取日期': today, '数据类型': self.dataType, '店铺名称': store['store_name'], '店铺访客数': '',
                                     '店铺浏览量': '', '商品访客数': '',
                                     '商品浏览量': '', '成交买家数': '', '成交订单数': '', '成交金额': '', '成交转化率': '',
                                     '客单价': '', '成交UV价值': '', '日期范围': date_range_text}
                        for index, card in enumerate(cards, 1):
                            try:
                                # 获取指标名称
                                alias_element = card.locator(".alias").first
                                alias = alias_element.inner_text()
                                safe_name = self.get_filename(alias) or f"unnamed_{index}"
                                if safe_name in llsj_dict:
                                    # 定位目标元素
                                    target_element = card.locator("p.valueWrapper >> span.value.__spider_font").first
                                    target_element.scroll_into_view_if_needed()
                                    screen_path = f"{img_path}/{safe_name}.png"
                                    target_element.screenshot(path=screen_path,timeout=5000,animations="disabled",caret="initial")
                                    llsj_dict[safe_name] = self.ocr_img_num(screen_path, exe_path)
                                    # 删除截图文件
                                    if os.path.exists(screen_path):
                                        os.remove(screen_path)
                            except Exception as e:
                                raise ValueError(f"OCR识别失败{str(e)}")
                        llsj_df = DataFrame([llsj_dict])
                        db.update_df_to_table(AppConfig.DB.STORE_DATA_TABLE,llsj_df)
                        self.status_signal.emit(f"{store['store_name']}-店铺数据已获取")
                        task_status['store_s'] += 1
                    except Exception as e:
                        task_status['store_e'] += 1
                        self.logger.error(f'[{store["store_name"]}]-店铺数据获取失败:{str(e)}')

                    if self.productBox and len(products) > 0:
                        product_ids_list = [p['product_id'] for p in products if p['store_name'] == store['store_name']]
                        if len(product_ids_list) > 0:
                            self.status_signal.emit(f"[{store['store_name']}]-开始获取商品数据")
                            for idx, product in enumerate(product_ids_list):
                                try:
                                    context = browser.contexts[0]
                                    page = context.pages[0]
                                    if idx == 0:
                                        page.goto(AppConfig.Chrome.PDD_SPGK_URL)
                                        page.wait_for_load_state()
                                        # 移除浏览器检测弹窗
                                        try:
                                            self.remove_element(page, 'id', "global_broswer_nav")
                                            self.remove_element(page, 'id', "umd_kits_home_entry_wrapper")
                                            page.wait_for_selector("div.ImportantList_msgbox-header__1hGG1", state="attached", timeout=3000)
                                            close_icon = page.query_selector("div.ImportantList_msgbox-header__1hGG1 > div > i")
                                            if close_icon:
                                                close_icon.click(timeout=3000)
                                        except Exception as e:
                                            pass

                                    input_container = page.query_selector("div[data-testid='beast-core-input']")
                                    input_container.scroll_into_view_if_needed()
                                    # 定位到第二个 beast-core-radioGroup
                                    radio_group = page.query_selector(
                                        "div[data-testid='beast-core-radioGroup']:nth-match(div[data-testid='beast-core-radioGroup'], 2)")
                                    yesterday_option = radio_group.query_selector(f"label:has-text('{self.dataType}')")
                                    yesterday_option.click(force=True)
                                    page.wait_for_load_state()
                                    time.sleep(random.randint(self.min_wait, self.max_wait))
                                    # 在该元素内部找到 input 元素
                                    input_element = input_container.query_selector("input")
                                    # 清空输入框中的值
                                    input_element.fill("")
                                    input_element.type(product, delay=100)

                                    suffix_element = input_container.query_selector("div[data-testid='beast-core-input-suffix']")
                                    suffix_element.click(timeout=5000)
                                    time.sleep(random.randint(self.min_wait, self.max_wait))
                                    # 移除“数据详情”和右侧悬浮按钮的遮挡元素
                                    page.evaluate("""
                                                     () => {
                                                    const element = document.getElementById('umd_kits_home_entry');
                                                        if (element) {
                                                            element.remove();
                                                        }
                                                    const elements = document.querySelectorAll('td');
                                                    elements.forEach(el => {
                                                        if (el.textContent.includes('数据详情')) {
                                                            el.remove();
                                                        }
                                                    });
                                                }
                                                """)
                                    tr_element = page.locator("tr[data-testid='beast-core-table-body-tr']")
                                    # 获取 tr 内除第一个 td 外的所有 td 元素
                                    td_elements = tr_element.locator("td:not(:first-child)").all()
                                    name_list = ['商品访客数', '商品浏览量', '成交金额', '成交件数', '成交订单数','成交买家数', '成交转化率']
                                    spsj_dict = {'获取日期': today, '数据类型': self.dataType,'商品ID':product,'商品描述':'','店铺名称': store['store_name'],
                                                 '商品访客数': 0, '商品浏览量': 0, '成交金额': 0, '成交件数': 0,'成交订单数': 0, '成交买家数': 0, '成交转化率': '0%', '日期范围': date_range_text}

                                    for index, td in enumerate(td_elements, 0):
                                        if index > 6:
                                            break
                                        safe_name = name_list[index]
                                        try:
                                            target_element = td.locator("div > div > span").first
                                            # 确保目标元素可见
                                            target_element.scroll_into_view_if_needed()
                                            # 截图并保存
                                            screen_path = f"{img_path}/{safe_name}.png"
                                            target_element.screenshot(path=screen_path, timeout=5000,animations="disabled", caret="initial")
                                            spsj_dict[safe_name] = self.ocr_img_num(screen_path, exe_path)
                                            # 删除截图文件
                                            if os.path.exists(screen_path):
                                                os.remove(screen_path)
                                        except Exception as e:
                                            raise ValueError(f"OCR识别失败{str(e)}")
                                    spsj_df = DataFrame([spsj_dict])
                                    db.update_df_to_table(AppConfig.DB.PRODUCT_DATA_TABLE, spsj_df)
                                    self.status_signal.emit(f"[{store['store_name']}]-商品:[{product}]数据已获取")
                                    task_status['product_s'] += 1
                                except Exception as e:
                                    task_status['product_e'] += 1
                                    self.logger.error(f"[{store['store_name']}]-商品:[{product}]数据获取失败:{str(e)}")
        except Exception as e:
            self.logger.error(f'本次获取数据任务失败:{str(e)}')
        finally:
            try:
                context.close()
                browser.close()
                playwright.stop()
            except:
                pass
            self.status_signal.emit(f"准备就绪,上次任务执行结果:店铺[成功{task_status['store_s']}/失败{task_status['store_e']}],商品[成功{task_status['product_s']}/失败{task_status['product_e']}]")
            self.finished.emit()